﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Toom.CellularAutomata.Debug;
using Toom.CellularAutomata.Common;


namespace Toom.CellularAutomata.Model
{
	public class CAFieldChunkManager
	{

		#region CTOR
		public CAFieldChunkManager( CAField field )
		{
			this.field = field;

			// how many chunks/tiles will we need?

			// horizontally:
			nrTilesX = field.sizeX / CAFieldChunk.CHUNK_SIZE;
			if ( field.sizeX % CAFieldChunk.CHUNK_SIZE > 0 )
				nrTilesX++;
	
			// vertically:
			nrTilesY = field.sizeY / CAFieldChunk.CHUNK_SIZE;
			if ( field.sizeY % CAFieldChunk.CHUNK_SIZE > 0 )
				nrTilesY++;

			// allocate!
			chunks = new CAFieldChunk[nrTilesX * nrTilesY];

			for ( int yChunk=0; yChunk<nrTilesY; yChunk++ )
				for ( int xChunk=0; xChunk<nrTilesX; xChunk++ )
				{
					int idx = ConvertChunkXYtoChunkIdx(xChunk, yChunk);
					chunks[idx] = CreateChunk( idx );
					CADebugger.DebugPrintLine( CADebugger.DebugMessageSource.FieldChunks, "Creating chunk idx=" + idx + " xyChunk=" + xChunk + "," + yChunk );
					CADebugger.DebugPrintLine( CADebugger.DebugMessageSource.FieldChunks, "checking for null: " + chunks[idx] );
				}
		}
		
		#endregion


		#region DATA

		//public int sizeX, sizeY;
		public CAField field;
		public int nrTilesX, nrTilesY;
		public CAFieldChunk[] chunks;

		#endregion


		#region helpers for conversion idx<->x,y
		public int ConvertXYtoChunkIdx(int x, int y, bool doAutoWraparound=true )
		{
			if ( doAutoWraparound )
				WraparoundXY( ref x, ref y );

			//if ( x<0 || x>=field.sizeX || y<0 || y>=field.sizeY )
			//	throw new Exception( "requested coordinates "+x+","+y+" are invalid. Field size is "+field.sizeX+","+field.sizeY );

			int xChunk = x / CAFieldChunk.CHUNK_SIZE;
			int yChunk = y / CAFieldChunk.CHUNK_SIZE;

			return yChunk * nrTilesX + xChunk;
		}
		public void ConvertChunkIdxToBaseXY( int idx, out int x, out int y )
		{
			if ( idx < 0 || idx>=chunks.Length )
				throw new Exception( "requested chunk idx "+idx+" is invalid. my size is "+chunks.Length );

			int yChunk = idx / nrTilesX;
			int xChunk = idx % nrTilesX;

			x = xChunk * CAFieldChunk.CHUNK_SIZE;
			y = yChunk * CAFieldChunk.CHUNK_SIZE;
		}

		public int ConvertChunkXYtoChunkIdx( int xChunk, int yChunk )
		{
			// wraparound
			WraparoundChunkXY( ref xChunk, ref yChunk );

			return yChunk * nrTilesX + xChunk;
		}

		public void ConvertChunkIdxToChunkXY( int idx, out int xChunk, out int yChunk )
		{
			if ( idx < 0 || idx>=chunks.Length )
				throw new Exception( "requested chunk idx "+idx+" is invalid. my size is "+chunks.Length );

			xChunk = idx % nrTilesX;
			yChunk = idx / nrTilesX;
		}

		#endregion


		#region Wraparound fixers

		public void WraparoundXY( ref int x, ref int y )
		{
			if ( x<0 )
				x+=field.sizeX;
			else if ( x>=field.sizeX )
				x-=field.sizeX;

			if ( y<0 )
				y+=field.sizeY;
			else if ( y>=field.sizeY )
				y-=field.sizeY;
		}

		public void WraparoundChunkXY( ref int xChunk, ref int yChunk )
		{
			if ( xChunk<0 )
				xChunk+=nrTilesX;
			else if ( xChunk>=nrTilesX )
				xChunk-=nrTilesX;

			if ( yChunk<0 )
				yChunk+=nrTilesY;
			else if ( yChunk>=nrTilesY )
				yChunk-=nrTilesY;
		}

		#endregion


		#region data set and get

		public int GetDataAt(int x, int y)
		{
			// wraparound (2do: what if we are so far beyond the field, that we need to adjust several times?)
			WraparoundXY( ref x, ref y );


			int idx = ConvertXYtoChunkIdx(x, y, false);	// does wraparound internally
			CAFieldChunk chunk = chunks[ idx ];

			if (chunk.state == CAFieldChunkState.Active)
				return chunk.GetDataAbsolute(x, y);
			else
				return CAFieldChunk.FILLER_CELL_VALUE;
		}
		public void SetDataAt(int x, int y, int newVal)
		{
			int idx = ConvertXYtoChunkIdx(x, y);
			CAFieldChunk chunk = chunks[idx];

			chunk.SetDataAbsolute(x, y, newVal);
		}

		public void SetNewDataAt( int x, int y, int newVal )
		{
			int idx = ConvertXYtoChunkIdx( x, y );
			CAFieldChunk chunk = chunks[idx];

			chunk.SetNewDataAbsolute( x, y, newVal );
		}

		#endregion


		#region GotoNewTime

		public void GotoNewTime()
		{
			for (int i = 0; i < chunks.Count(); i++)
				if ( chunks[i].state == CAFieldChunkState.Active )
					chunks[i].GotoNewTime();
		}

		#endregion


		#region GetUsefulArea
		/// <summary>
		/// Rerurns the set of Rectangles which cover only the area where data currently is.
		/// </summary>
		/// <returns></returns>
		public List<CARectangle> GetUsefulArea( CAOptimization opt )
		{
			List<CARectangle> useful = new List<CARectangle>();

			if ( opt == CAOptimization.None )
			{
				useful.Add( new CARectangle( 0, 0, field.sizeX, field.sizeY ) );
				return useful;
			}

			// optimization is Life

			for ( int yChunk=0 ; yChunk < nrTilesY ; yChunk++ )
				for ( int xChunk=0 ; xChunk < nrTilesX ; xChunk++ )
				{
					int i = ConvertChunkXYtoChunkIdx( xChunk, yChunk );
						//y * nrTilesX + x;
					bool skip = true;

					if ( chunks[i].state == CAFieldChunkState.Virgin )
					{
						// check all 8 neighbors. if any active, we need to process this chunk
						foreach ( int neighborIdx in chunks[i].neighbors )
						{
							if ( chunks[neighborIdx].state == CAFieldChunkState.Active )
							{
								// mark our chunk as Warm and process it
								chunks[i].state = CAFieldChunkState.Warm;
								skip=false;
								break;
							}
						}
					}
					else
						skip = false;

					if ( skip )
						continue;

					//int x0 = chunks[i].mybaseX;
					//int y0 = chunks[i].mybaseY;
					int x0 = xChunk * CAFieldChunk.CHUNK_SIZE;
					int y0 = yChunk * CAFieldChunk.CHUNK_SIZE;
					int xWidth  = CAFieldChunk.CHUNK_SIZE;
					int yHeight = CAFieldChunk.CHUNK_SIZE;
					useful.Add( new CARectangle( y0, x0, xWidth, yHeight ) );
				}

			return useful;
		}
		#endregion


		#region Debugging
		public string GetStats()
		{
			StringBuilder sb = new StringBuilder();

			// values stats
			//int[] values = new int[field.valMax+1];
			Dictionary<int,int> valueCounts = new Dictionary<int, int>();

			for ( int y=0 ; y<field.sizeY ; y++ )
				for ( int x=0 ; x<field.sizeX ; x++ )
				{
					int v = GetDataAt( x, y );

					if ( !valueCounts.ContainsKey( v ) )
						valueCounts.Add( v, 0 );

					valueCounts[v]++;
				}

			//for ( int vIdx=field.valMin ; vIdx<=field.valMax ; vIdx++ )
			foreach( int v in valueCounts.Keys )
				sb.AppendLine( "Value "+v+" was found " + valueCounts[v] + " times." );

			// chunks stats

			int[] nrChunksInStates = new int[3];
			for ( int i=0 ; i<chunks.Count() ; i++ )
				nrChunksInStates[ (int)chunks[i].state ]++;

			sb.AppendLine( "We have a total of " + chunks.Count() + " chunks." );
			for ( int idxState=0 ; idxState<nrChunksInStates.Count() ; idxState++ )
				sb.AppendLine( "There are " + nrChunksInStates[idxState] + " chunks in state " + (CAFieldChunkState)idxState );

			sb.AppendLine();
			sb.AppendLine( "Map:" );

			{
				string s;

				if ( nrTilesX >= 10 )
				{
					sb.Append( "    " );
					s = "          1         2         3         4         5         6         7";
					if ( s.Length < nrTilesX )
						sb.AppendLine( s );
					else
						sb.AppendLine( "          1         2         3         4         5         6         7".Substring( 0, nrTilesX ) );
				}

				sb.Append( "    " );
				s = "01234567890123456789012345678901234567890123456789012345678901234567890";
				if ( s.Length < nrTilesX )
					sb.AppendLine( s );
				else
					sb.AppendLine( "          1         2         3         4         5         6         7".Substring( 0, nrTilesX ) );
			}

			for ( int yChunk=0 ; yChunk<nrTilesY ; yChunk++ )
			{
				sb.AppendFormat( "{0:000} ", yChunk );
				for ( int xChunk=0 ; xChunk<nrTilesX ; xChunk++ )
				{
					int idx = ConvertChunkXYtoChunkIdx( xChunk, yChunk );
					if ( chunks[idx].state == CAFieldChunkState.Virgin )
						sb.Append( "v" );
					else if ( chunks[idx].state == CAFieldChunkState.Warm )
						sb.Append( "w" );
					else
						sb.Append( "A" );
				}
				sb.AppendLine();
			}

			return sb.ToString();
		}

		#endregion



		#region HELPERS

		private CAFieldChunk CreateChunk( int idx )
		{
			int baseX, baseY;
			ConvertChunkIdxToBaseXY( idx, out baseX, out baseY );

			CAFieldChunk chunk = new CAFieldChunk( baseX, baseY );
			chunks[idx] = chunk;

			// setup neighbors
			for ( int dy=-1; dy<=1; dy++)
				for ( int dx=-1; dx<=1; dx++)
				{
					if ( dy==0 && dx==0 ) continue;

					int xNeighbor = baseX + dx*CAFieldChunk.CHUNK_SIZE;
					int yNeighbor = baseY + dy*CAFieldChunk.CHUNK_SIZE;

					int idxNeighbor = ConvertXYtoChunkIdx( xNeighbor, yNeighbor );

					// if neighbor is out of bounds, then we skip it
					if ( idxNeighbor<0 || idxNeighbor >= chunks.Length )
						//continue;
						throw new Exception( "CAFCM.CreateChunk: idxNeighbor=" + idxNeighbor+ " is invalid" );

					chunk.neighbors[chunk.neighborsCount++] = idxNeighbor;
				}

			return chunk;
		}//method CreateChunk()

		#endregion


	}//class CAFieldChunkManager


	#region struct CARectangle

	public struct CARectangle
	{
		public int top;
		public int left;
		public int width;
		public int height;

		public CARectangle(int top, int left, int width, int height)
		{
			this.top = top;
			this.left = left;
			this.width = width;
			this.height = height;
		}
	}

	#endregion

}//ns
